Usa pacman
para cargar las librerías e instalarlas si no están disponibles
Todos los ejercicios planteados están relacionados con el uso de las
librerías dplyr, tidyr y lubridate. Familiarízate con
el uso del operador %>% para facilitar la compresión
del código. Los ejercicios consideran que las librería están instaladas
y cargadas.
Fichero de datos.
Todas las preguntas están referidas al fichero
2024-03-12.Rdata, que contiene información de los
registros (logs) de las descargas realizadas del repositorio CRAN en esa
fecha. La información contenida en el fichero es la siguiente:
- date
- time (in UTC)
- size (in bytes)
- r_version, version of R used to download package
- r_arch (i386 = 32 bit, x86_64 = 64 bit)
- r_os (darwin9.8.0 = mac, mingw32 = windows)
- package
- country, two letter ISO country code. Geocoded from IP using
[MaxMind’s][https://dev.maxmind.com/geoip/geoip2/geolite2/] free
database
- ip_id, a daily unique id assigned to each IP address
El material está basado en el curso ‘Getting and Cleaning data’ de la
librería swirlr
Se pueden descargar ficheros actuales del logs usando la
librería https://r-hub.github.io/cranlogs/
- Carga el fichero de datos y almacénalo en un data frame llamado
mydf. Asegúrate que la opción
stringsAsFactors=FALSE. Mira las dimensiones y los primeros
datos.
Nota: El siguiente bloque puede tardar en ejecutarse
por lo que solo lo ejecutaremos una vez. Después usaremos la selección
de registros almacenada en el fichero Rdata.
# Fuente de datos http://cran-logs.rstudio.com/
mydf <- read_csv("./data/2024-03-12.csv")
set.seed(seed=14032024)
N<-200000
I<-sample(1:nrow(mydf),size=N,replace=FALSE)
mydf<-mydf[I,]
# Guardamos en formato binario, la primera vez que cargamos
save(file = './data/2024-03-12.Rdata',list=c('mydf'))
Después de la primera carga poner eval=FALSE en el
chunk anterior para acelerar la ejecución.
# Cargamos el fichero binario
load(file ='./data/2024-03-12.Rdata')
dim(mydf)
[1] 200000 10
head(mydf)
mydf %>% head
mydf %>% tail
mydf %>% str
tibble [200,000 × 10] (S3: tbl_df/tbl/data.frame)
$ date : Date[1:200000], format: "2024-03-12" "2024-03-12" ...
$ time : 'hms' num [1:200000] 48036 63761 70942 43440 ...
..- attr(*, "units")= chr "secs"
$ size : num [1:200000] 103412 526554 1280374 375281 160694 ...
$ r_version: chr [1:200000] "4.3.3" "4.3.3" "4.1.2" NA ...
$ r_arch : chr [1:200000] "x86_64" "x86_64" "x86_64" NA ...
$ r_os : chr [1:200000] "mingw32" "mingw32" "linux-gnu" NA ...
$ package : chr [1:200000] "callr" "jquerylib" "colourpicker" "devtools" ...
$ version : chr [1:200000] "3.7.5" "0.1.4" "1.3.0" "2.4.5" ...
$ country : chr [1:200000] NA "AT" "ET" "GB" ...
$ ip_id : num [1:200000] 27 22280 16115 3 6190 ...
mydf %>% dim
[1] 200000 10
- Para facilitar el manejo transforma los datos en un tibble
as_tibble y almacena el resultado con el nombre
cran, y borra el data frame original (usa
rm)
# Transformar el data frame en un tibble
cran <- as_tibble(mydf)
# Eliminar el data frame original para liberar memoria
rm(mydf)
- Una ventaja de trasformar en tibble es la visualización. Escribe
directamente el nombre de la variable y observa la visualización.
Posteriormente usa la función
glimpse sobre
cran y observa el resultado.
# Visualizar el tibble directamente
cran
# Usar la función glimpse para obtener un resumen detallado
glimpse(cran)
Rows: 200,000
Columns: 10
$ date <date> 2024-03-12, 2024-03-12, 2024-03-12, 2024-03-12, 2024-03-12, 2024…
$ time <hms> 48036 secs, 63761 secs, 70942 secs, 43440 secs, 5385 secs, 1810 s…
$ size <dbl> 103412, 526554, 1280374, 375281, 160694, 227646, 1358663, 200365,…
$ r_version <chr> "4.3.3", "4.3.3", "4.1.2", NA, NA, "4.2.0", "4.3.3", "4.3.3", "4.…
$ r_arch <chr> "x86_64", "x86_64", "x86_64", NA, NA, "x86_64", "x86_64", "x86_64…
$ r_os <chr> "mingw32", "mingw32", "linux-gnu", NA, NA, "mingw32", "mingw32", …
$ package <chr> "callr", "jquerylib", "colourpicker", "devtools", "glue", "magrit…
$ version <chr> "3.7.5", "0.1.4", "1.3.0", "2.4.5", "1.7.0", "2.0.3", "0.5.2", "1…
$ country <chr> NA, "AT", "ET", "GB", "US", "CN", NA, "US", NA, "KR", "US", "US",…
$ ip_id <dbl> 27, 22280, 16115, 3, 6190, 42486, 27, 8763, 27, 20, 412, 2157, 27…
dplyr
No almacenes el resultado de las operaciones, salvo que sea
necesario
- Elige únicamente las variables
ip_id, package, country
# Seleccionar solo las columnas ip_id, package y country
cran_selected <- cran %>%
select(ip_id, package, country)
# Ver el resultado
head(cran_selected)
NA
- Elige todas las variables entre
r_arch y country
cran_range <- cran %>%
select(r_arch:country)
head(cran_range)
NA
- Puedes usar el signo (-) para no coger una columna determinada.
Selecciona todas las columnas menos las que están entre
date y size
cran_excluded <- cran %>%
select(-(date:size))
head(cran_excluded)
NA
- Elige aquellos registros en los que se haya descargado el paquete
plotly, desde un sistema que ejecuta la versión de R
3.5.1
cran_plotly <- cran %>%
filter(package == "plotly", r_version == "3.5.1")
head(cran_plotly)
NA
- Elige aquellas descargas realizadas desde España (ES), con versiones
de R superiores a la 3.4.0
cran_es <- cran %>%
filter(country == "ES", r_version > "3.4.0")
head(cran_es)
NA
- Elige aquellas descargas realizadas desde España (ES), o Portugal
(PT).
cran_es_pt <- cran %>%
filter(country %in% c("ES", "PT"))
head(cran_es_pt)
NA
- Elige aquellas descargas cuyo tamaño supera los 100500 bytes desde
un sistema operativo
linux-gnu.
cran_tamano <- cran %>%
filter(size > 100500, r_os == "linux-gnu")
cran_tamano
- Elige aquellas descargas en las que la versión del sistema operativo
no está vacía.
descargas_filtradas <- cran %>%
filter(if_all(everything(), ~ . != ""))
descargas_filtradas
- Almacena en
cran2 todas las columnas entre
size y ip_id.
cran2 <- cran%>%
select(size:ip_id)
cran2
- Ordena
cran2 en orden ascendente según la variable
ip_id.
cran2_asc <- cran2 %>%
arrange(ip_id)
cran2_asc
- Ordena
cran2 en orden descendente según la variable
ip_id.
cran2_desc <- cran2 %>%
arrange(desc(ip_id))
cran2_desc
- Ordena
cran2 según los valores de
package e ip_id.
cran2_ordenada <- cran2 %>%
arrange(package, ip_id)
cran2_ordenada
- Ordena
cran2 según los valores de country
(ascendente), r_version (desdencente) e
ip_id(ascendente).
cran2_ordenada2 <- cran2 %>%
arrange(desc(r_version), country, ip_id)
cran2_ordenada2
NA
- Selecciona el subconjunto formado por las variables, del
cran, ip_id, package y size en este orden y
almacénalas en cran3.
cran3 <- cran %>%
select(ip_id, package, size)
- Crea una nueva variable en
cran3 llamada
size_mb que contenga el tamaño de la descarga expresado en
Mbytes (\(1Mbyte=2^{20}\) bytes).
cran3 <- cran3%>%
mutate((size_mb = size / 2^20))
cran3
NA
- Sabiendo que 1Gb son \(2^{10}\)
Mbytes, crea una nueva variable en
cran3 llamada
size_gb que contenga el tamaño de la descarga expresado en
Gbytes.
cran3 <- cran3 %>%
mutate(size_gb = size / (2^20 * 2^10))
cran3
- Considera que se ha detectado un error en el sistema donde todos los
tamaños de las descargas son 1000 bytes menores de lo que corresponde.
Crea una nueva variable llamada
correct_size con el valor
correcto.
cran3 <- cran3 %>%
mutate(correct_size = size + 1000)
cran3
- Usa
summarise para determinar el valor medio del tamaño
de las descargas realizadas en cran y almacenálo en la
variable avg_bytes
avg_bytes <- cran %>%
summarise(avg_bytes = mean(size, na.rm = TRUE))
avg_bytes
Agrupamientos
- Crea un agrupamiento del tibble
cran según la variable
package y almacénalo en la variable
by_package. Observa el nuevo elemento creado y aunque
aparentemente no hay cambio al principio, se indica que se ha hecho un
agrupamiento. Éste agrupamiento se puede deshacer con la función
ungroup.
by_package <- cran %>%
group_by(package)
by_package
by_package_ungrouped <- by_package %>%
ungroup()
- Usa la función
summarise sobre el elemento agrupado y
calcula el valor medio de la variable size. Observa el
efecto del agrupamiento en el resultado.
mean_size_package <- by_package %>%
summarise(mean_size = mean(size, na.rm = TRUE))
mean_size_package
- Para el agrupamiento creado calcula:
- . count = n()
- . unique = n_distinct(ip_id)
- . countries = n_distinct(country)
- . avg_bytes = mean(size)
número de elementos; n(), elementos
distintos,n_distinct() de la variables
ip_id y country, y el la mediana (función
stats::median) de la variable size y
almacénalos en las variables
count, unique, countries y med_bytes respectivamente. El
resultado del sumario se almacenará en pack_sum.
- Muestra el 1% de los paquetes que han sido más descargados. Para
ello puedes utilizar la función
quantile(????, probs=0.99).
Siendo ??? la variable de interés, que te permitirá estimar el umbral.
Los valores por encima serán los que nos interesen. Almacena estos
paquetes top descargas en la variable
top_counts. NOTA: De ahora en adelante concatena
todas las operaciones partiendo del elemento cran, usando
pipes.
- Repite el apartado anterior utilizando pipes para
que finalmente el resultado se ordene, en orden descendente, según el
número de descargas (
count) y se almacene el la variable
top_counts_sorted
- En lugar de utilizar el recuento de descargas, es más lógico
considerar el número de ordenadores distintos en los que se han
descargado las librerías, para ello utilizaríamos la columna
unique que cuenta solo una descarga por ordenador,
independientemente del número de veces que se descarge una librería.
Repite los cálculos considerando esta variable y almacena el resultado
en top_unique_sorted.
- Completa lo que se indica usando
pipes. Selecciona las
columnas ip_id, country, package, size de cran
y muestra el resultado por pantalla usando print. No es
necesario almacenar.
- Añade una columna llamada
size_mb que contenga el
tamaño de cada descarga en megabytes.
- Selecciona aquellas descargas cuyo tamaño es menor o igual que 0.5
Mbytes
- Repite el apartado anterior ordenando los resultados, en orden
descendente según el tamaño en Megabytes y muéstralo por pantalla.
EXTRA. Se desea mostar gráficamente aquellos países
que suponen el mayor tráfico de información, ocasionado por el total de
descargas acumuladas. Para facilitar la visualización céntrate en
aquellos que están por encima del 90 % del top de descargas. El gráfico
debe mostrar los valores de forma ordenada de mayor a menor. En el eje X
debe aparecer el código de país y en el eje Y el tráfico generado.
Intenta hacerlo usando una tubería sin emplear ninguna variable
auxiliar. cran%>%...+geom_point()
EXTRA Tal como se indica en el ejercicio, el
significado de los códigos usados para la codificación de los paise se
pueden obtener de https://dev.maxmind.com/geoip/geoip2/geolite2/. Descarga
el fichero GeoLite2 Country en formato CSV. Este
fichero contiene información en varios idiomas. Usa
GeoLite2-Country-Locations-es.csv que tiene la información
en español. Deseamos poder determinar las descargas por continentes por
lo que necesitamos añadir esta información a los datos de las descargas
almacenados en cran. Procede de la siguiente forma:
- Descarga el fichero de códigos de paises
GeoLite2-Country-Locations-es.csv e impórtalo en el
dataframe code_paises. OJO con la
codificación
- Carga y almacena la información del fichero de los paises.
- Elimina todos los registros de
cran que no incluyan el
código del país.
- Elimina los registros de
code_paises que no incluyen el
código del país.
- Combina los dos data frames para añadir a
cran el
código del país y el continente, el resto de variables no son
necesarias.
- Representa un diagrama de barras con el flujo de datos, por
continente, ordenados de mayor a menor. Expresa el flujo de datos en
Terabytes (1Tb=2^40bytes)
tidyr.
Carga el fichero de datos datostidyr.Rdata que contiene
varios conjuntos de datos que vamos a usar en los siguientes
ejercicios.
- Observa el data frame
students : grade male female 1 A
1 5 2 B 5 0 3 C 5 2 4 D 5 5 5 E 7 4
La columna grade es la calificación y las dos siguientes
el número de estudiantes hombre y mujer que han recibido dicha
calificación. ¿ Cuáles son las variables ?. Transfórmalo en un conjunto
tidy con las variables, grade, sex y count
- Considera el conjunto ´students2´, que es similar al primero pero
que se han recogido datos correspondientes a dos grupos,
(
male_1, female_1, etc). Transfórmalo en un conjunto
tidy añadiendo una columna adicional correspondiente al
grupo (llama a la nueva variable class)
- Considera el conjunto
students3 y observa por qué no es
un tidy data . Las variables finales del conjunto deberían
ser: name, class, midterm, final. Dado que un alumno solo
puede estar en una de las clases aparecen muchos NA, al
reorganizar, usa na.rm=TRUE en las opciones de
gather
- Considera que la columna
class queremos especificarla
con valores, 1,2,3,4 y 5 en lugar de class1, …, class5. En librería de
importación de datos (readr) se proporcionan funciones que
permite identificar y extraer diversos tipos de datos. Son las funciones
parse_XXXX. Observa el efecto de aplicar
parse_number('class5') y utiliza el resultado para realizar
la tarea solicitada.
- Observa el conjunto
students4 donde se presenta el
problema de tener varias observaciones en la misma tabla. Separa el
conjunto en dos tablas, una que contenga información relativa a los
estudiantes, que llamarás student_info, que no debe
contener repeticiones, y otra con las calificaciones y la clave primaria
que enlaza ambas tablas(id), que llamarás
gradebook.
- Consideremos el caso de una observación almacenada en varias tablas.
Observa los datos almacenados en
failed y
passed con la información de los alumnos que suspeden
(calificaciones C,D,E,F) y los que aprueban (calificaciones A, B). Debes
unir ambos conjuntos en uno que contenga una columna adicional llamada
status que indicará passed o
failed según la situación de cada alumno, puedes usar la
función bind_rows.
Pongamos todo en
común.
- El SAT es un examen popular de preparación para la universidad en
los Estados Unidos que consta de tres partes: lectura crítica,
matemáticas y escritura. Los estudiantes pueden obtener hasta 800 puntos
en cada parte. Este conjunto de datos presenta el número total de
estudiantes, para cada combinación de parte y sexo del estudiante.
Considera los datos almacenado en
sat y haz las
transformaciones adecuadas para que las columnas del conjunto
tidy sean score_range, part, sex, count en ese
orden. Ten en cuenta que los datos totales no es necesario almacenarlos
ya que se pueden generar a posteriori.
- Utiliza agrupamientos, según las variables
part, sex y
genera dos nuevas columnas que contengan el número total de estudiantes
en dicho grupo, y la proporción dentro del grupo. Almacénalas en las
variables total y prop respectivamente.
- Representa gráficamente el conjunto obtenido para mostrar las
diferencias entre hombres y mujeres, en las diferentes pruebas para cada
una de las franjas de calificación.
Manejo de fechas con
lubridate.
- La función
today devuelve la fecha actual. Almacéna la
fecha actual en this_day, y muéstrala por pantalla. Usa las
funciones year, month y day para extraer cada una de las
partes. wday para obtener el día de la semana ()
- Guarda el instante actual en una variable llamada
this_moment con la función now y muéstralo por
pantalla. Usa las funciónes hour, minute, second para
extraer la información de cada elemento.
- La librería
lubridate dispone de múltiples funciones
para importar en formato fecha datos con un gran variedad de formatos de
entrada ymd(), dmy(), hms(), ymd_hms() donde cada letra
indica years (y),months (m), days (d), hours (h), minutes (m), y
seconds (s). Prueba my_date <- ymd(“2019-02-14”), y determina de
que clase es my_date. Las funciones son muy robustas y
pueden intepretar correctamente diversos formato. Prueba
ymd() para importar la fecha “1989 May
17”. ¿Cómo importarías “March 12, 1975”?
- Importa el valor numérico
25081985, sabiendo que el
formato es dia, mes, año, y posteriormente 192012 donde se
ha almacenado año, mes y día. Modifica el campo fecha en el segundo caso
añadiendo algún carácter separador para eliminar la ambigüedad.
fecha<-'19200102'
ymd(fecha)
- Almacena la cadena
2014-08-23 17:23:02 en la variable
dt1 e impórtala para que incluya la fecha y la
hora,posteriormente importa la cadena "03:22:14"
(hh:mm:ss)
- Importa el vector de fechas almacenado el
dt2
- Podemos reasingar nuevos valores a una variable fecha/hora con la
función
update , por ejemplo:
update(this_moment, hours = 8, minutes = 34, seconds = 55).
Actualiza la variable this_moment creada anteriormente para
que contenga la hora actual.
- Zonas horarias. Considera que estás en Nueva York,
que planeas visitar a un amigo en Hong Kong y que has perdido tu
itinerario, pero sabes que tu vuelo sale de Nueva York a las 17:34
(17:34 horas) pasado mañana. También sabes que el vuelo está programado
para llegar a Hong Kong exactamente 15 horas y 50 minutos después de la
salida. Vamos a reconstruir el itinerario:
- Genera la fecha actual, con
now e indica la zona
horaria de Nueva York(America/New_York) (la función
OlsonNames() devuelve todas las zonas horarias) y
almacénala en la variable nyc.
- Añade dos días y almacénalo en la variable
salida.
Puedes usar la función days y sumar directamente. Comprueba
que la salida tiene el valor correcto.
- Actualiza la variable salida con la hora real de salida.
- Averigua a qué hora llegarás a Hong Kong, con referencia horaria de
NY.
- Averigua a qué hora llegarás a Hong Kong, con referencia horaria
local. Usa la función
with_tz para realizar esta
tarea.
- La última vez que viste a tu amigo fue en Singapur el 17 de Junio de
2008. Genera dicha fecha en zona horaria de Singapur y utiliza la
funcion
interval para crear el tramo temporal entre ambas
fechas y aplica la función as.period sobre el intervalo
resultante para calcular exáctamente cuánto tiempo hace desde que os
visteis. Cuando consideramos zonas horarias, peridos que incluyen años
bisiestos, etc. El manejo de fecha requiere de librerías como
lubridate para facilitar estas tareas.
LS0tDQp0aXRsZTogIkNhcnJlcmEgVGlkeXZlcnNlLiBQcmVndW50YXMgQ29ydGFzLk1BUkNFTElOTyBNQVJUSU5FWiINCnN1YnRpdGxlOiBUcmF0YW1pZW50byBkZSBEYXRvcy4gR3JhZG8gZW4gQ2llbmNpYSBkZSBEYXRvcy0gVVYNCmF1dGhvcjogIk1hcmNlbGlubyBNYXJ0w61uZXoiDQpkYXRlOiAgImByIFN5cy5EYXRlKClgIiAgI1BvbmRyw61hIGxhIGZlY2hhIGRlbCBkw61hIGFjdHVhbA0KcGFyYW1zOg0KICBsYW5nOiBFUw0KbGFuZzogImByIHN3aXRjaChwYXJhbXMkbGFuZywgRVMgPSAnZXMtRVMnLCBFTiA9ICdlbi1VUycpYCINCm91dHB1dDoNCiAgcGRmX2RvY3VtZW50Og0KICAgIHRvYzogbm8NCiAgICB0b2NfZGVwdGg6IDMNCiAgICBudW1iZXJfc2VjdGlvbnM6IHllcw0KICBodG1sX2RvY3VtZW50Og0KICAgIGVjaG86IHllcw0KICAgIG51bWJlcl9zZWN0aW9uczogeWVzDQogICAgdGhlbWU6IGx1bWVuDQogICAgdG9jOiB5ZXMNCiAgaHRtbF9ub3RlYm9vazoNCiAgICBlY2hvOiB5ZXMNCiAgICBudW1iZXJfc2VjdGlvbnM6IHllcw0KICAgIHRvYzogeWVzDQpsYW5ndWFnZToNCiAgbGFiZWw6DQogICAgZmlnOiAnRmlndXJhICcNCiAgICB0YWI6ICdUYWJsYSAnDQogICAgZXE6ICdFY3VhY2nDs24gJw0KICAgIHRobTogJ1Rlb3JlbWEgJw0KICAgIGxlbTogJ0xlbWEgJw0KICAgIGRlZjogJ0RlZmluaWNpw7NuICcNCiAgICBjb3I6ICdDb3JvbGFyaW8gJw0KICAgIHBycDogJ1Byb3Bvc2ljacOzbiAnDQogICAgZXhtOiAnRWplbXBsbyAnDQogICAgZXhyOiAnRWplcmNpY2lvICcNCiAgICBwcm9vZjogJ0RlbW9zdHJhY2nDs24uICcNCiAgICByZW1hcms6ICdOb3RhOiAnDQogICAgc29sdXRpb246ICdTb2x1Y2nDs24uICcNCi0tLQ0KDQoNCmBgYHtyIHNldHVwLCBjYWNoZSA9IEYsIGVjaG8gPSBGLCBtZXNzYWdlID0gRiwgd2FybmluZyA9IEYsIHRpZHkgPSBGLH0NCg0KIyBDT05GSUdVUkFDScOTTiBHRU5FUkFMDQpsaWJyYXJ5KGtuaXRyKQ0Kb3B0aW9ucyh3aWR0aCA9IDEwMCkNCiMgT3BjaW9uZXMgZ2VuZXJhbGVzIGNodW5rcw0KDQojIFBBUkEgR0VORVJBUiBTT0xPIExPUyBFTlVOQ0lBRE8gaW5jbHVkZT1GQUxTRQ0Kb3B0c19jaHVuayRzZXQoZWNobz1GLG1lc3NhZ2UgPSBGLCBlcnJvciA9IEYsIHdhcm5pbmcgPSBGLCBjb21tZW50ID0gTkEsIGZpZy5hbGlnbiA9ICdjZW50ZXInLCBkcGkgPSAxMDAsIHRpZHkgPSBGLCBjYWNoZS5wYXRoID0gJy5jYWNoZS8nLCBmaWcucGF0aCA9ICcuL2ZpZ3VyZS8nLCBpbmNsdWRlPUZBTFNFLGZpZy5oZWlnaHQ9NiwgZmlnLndpZHRoPTgpDQoNCiMgUEFSQSBJbmNsdWlyIGxhIHNvbHVjaW9uZXMgU09MTyBMT1MgRU5VTkNJQURPIGluY2x1ZGU9VFJVRQ0Kb3B0c19jaHVuayRzZXQoZWNobz1ULG1lc3NhZ2UgPSBGLCBlcnJvciA9IEYsIHdhcm5pbmcgPSBGLCBjb21tZW50ID0gTkEsIGZpZy5hbGlnbiA9ICdjZW50ZXInLCBkcGkgPSAxMDAsIHRpZHkgPSBGLCBjYWNoZS5wYXRoID0gJy5jYWNoZS8nLCBmaWcucGF0aCA9ICcuL2ZpZ3VyZS8nLCBpbmNsdWRlPVRSVUUsZmlnLmhlaWdodD02LCBmaWcud2lkdGg9OCkNCg0KDQpgYGANCg0KIyBVc2EgYHBhY21hbmAgcGFyYSBjYXJnYXIgbGFzIGxpYnJlcsOtYXMgZSBpbnN0YWxhcmxhcyBzaSBubyBlc3TDoW4gZGlzcG9uaWJsZXMNCg0KYGBge3IsZWNobz1GQUxTRX0NCg0KbGlicmFyeShwYWNtYW4pDQpwX2xvYWQoZHBseXIsbHVicmlkYXRlKQ0KDQpgYGANCg0KVG9kb3MgbG9zIGVqZXJjaWNpb3MgcGxhbnRlYWRvcyBlc3TDoW4gcmVsYWNpb25hZG9zIGNvbiBlbCB1c28gZGUgbGFzIGxpYnJlcsOtYXMgKipkcGx5ciwgdGlkeXIgeSBsdWJyaWRhdGUqKi4gRmFtaWxpYXLDrXphdGUgY29uIGVsIHVzbyBkZWwgb3BlcmFkb3IgKiolPiUqKiBwYXJhIGZhY2lsaXRhciBsYSBjb21wcmVzacOzbiBkZWwgY8OzZGlnby4NCkxvcyBlamVyY2ljaW9zIGNvbnNpZGVyYW4gcXVlIGxhcyBsaWJyZXLDrWEgZXN0w6FuIGluc3RhbGFkYXMgeSBjYXJnYWRhcy4gIA0KDQojIEZpY2hlcm8gZGUgZGF0b3MuDQoNClRvZGFzIGxhcyBwcmVndW50YXMgZXN0w6FuIHJlZmVyaWRhcyBhbCBmaWNoZXJvICoqMjAyNC0wMy0xMi5SZGF0YSoqLCBxdWUgY29udGllbmUgaW5mb3JtYWNpw7NuIGRlIGxvcyByZWdpc3Ryb3MgKGxvZ3MpIGRlIGxhcyBkZXNjYXJnYXMgcmVhbGl6YWRhcyBkZWwgcmVwb3NpdG9yaW8gQ1JBTiBlbiBlc2EgZmVjaGEuIExhIGluZm9ybWFjacOzbiBjb250ZW5pZGEgZW4gZWwgZmljaGVybyBlcyBsYSBzaWd1aWVudGU6DQoNCg0KKiBkYXRlDQoqIHRpbWUgKGluIFVUQykNCiogc2l6ZSAoaW4gYnl0ZXMpDQoqIHJfdmVyc2lvbiwgdmVyc2lvbiBvZiBSIHVzZWQgdG8gZG93bmxvYWQgcGFja2FnZQ0KKiByX2FyY2ggKGkzODYgPSAzMiBiaXQsIHg4Nl82NCA9IDY0IGJpdCkNCiogcl9vcyAoZGFyd2luOS44LjAgPSBtYWMsIG1pbmd3MzIgPSB3aW5kb3dzKQ0KKiBwYWNrYWdlDQoqIGNvdW50cnksIHR3byBsZXR0ZXIgSVNPIGNvdW50cnkgY29kZS4gR2VvY29kZWQgZnJvbSBJUCB1c2luZyBbTWF4TWluZCdzXVtodHRwczovL2Rldi5tYXhtaW5kLmNvbS9nZW9pcC9nZW9pcDIvZ2VvbGl0ZTIvXSBmcmVlIGRhdGFiYXNlDQoqIGlwX2lkLCBhIGRhaWx5IHVuaXF1ZSBpZCBhc3NpZ25lZCB0byBlYWNoIElQIGFkZHJlc3MNCg0KRWwgbWF0ZXJpYWwgZXN0w6EgYmFzYWRvIGVuIGVsIGN1cnNvICdHZXR0aW5nIGFuZCBDbGVhbmluZyBkYXRhJyBkZSBsYSBsaWJyZXLDrWEgW3N3aXJscl0oaHR0cHM6Ly9zd2lybHN0YXRzLmNvbS8pIA0KDQpTZSBwdWVkZW4gZGVzY2FyZ2FyIGZpY2hlcm9zIGFjdHVhbGVzIGRlbCBgbG9nc2AgdXNhbmRvIGxhIGxpYnJlcsOtYSBodHRwczovL3ItaHViLmdpdGh1Yi5pby9jcmFubG9ncy8NCg0KMS4gIENhcmdhIGVsIGZpY2hlcm8gZGUgZGF0b3MgeSBhbG1hY8OpbmFsbyBlbiB1biBkYXRhIGZyYW1lIGxsYW1hZG8gYG15ZGZgLiBBc2Vnw7pyYXRlIHF1ZSBsYSBvcGNpw7NuIGBzdHJpbmdzQXNGYWN0b3JzPUZBTFNFYC4gTWlyYSBsYXMgZGltZW5zaW9uZXMgeSBsb3MgcHJpbWVyb3MgZGF0b3MuDQoNCioqTm90YSoqOiBFbCBzaWd1aWVudGUgYmxvcXVlIHB1ZWRlIHRhcmRhciBlbiBlamVjdXRhcnNlIHBvciBsbyBxdWUgc29sbyBsbyBlamVjdXRhcmVtb3MgdW5hIHZlei4gRGVzcHXDqXMgdXNhcmVtb3MgbGEgc2VsZWNjacOzbiBkZSByZWdpc3Ryb3MgYWxtYWNlbmFkYSBlbiBlbCBmaWNoZXJvIFJkYXRhLg0KDQoNCmBgYHtyLGV2YWw9RkFMU0V9DQojIEZ1ZW50ZSBkZSBkYXRvcyBodHRwOi8vY3Jhbi1sb2dzLnJzdHVkaW8uY29tLw0KbXlkZiA8LSByZWFkX2NzdigiLi9kYXRhLzIwMjQtMDMtMTIuY3N2IikNCg0Kc2V0LnNlZWQoc2VlZD0xNDAzMjAyNCkNCk48LTIwMDAwMA0KSTwtc2FtcGxlKDE6bnJvdyhteWRmKSxzaXplPU4scmVwbGFjZT1GQUxTRSkNCm15ZGY8LW15ZGZbSSxdDQojIEd1YXJkYW1vcyBlbiBmb3JtYXRvIGJpbmFyaW8sIGxhIHByaW1lcmEgdmV6IHF1ZSBjYXJnYW1vcw0Kc2F2ZShmaWxlID0gJy4vZGF0YS8yMDI0LTAzLTEyLlJkYXRhJyxsaXN0PWMoJ215ZGYnKSkNCmBgYA0KDQpEZXNwdcOpcyBkZSBsYSBwcmltZXJhIGNhcmdhIHBvbmVyICoqZXZhbD1GQUxTRSoqIGVuIGVsICpjaHVuayogYW50ZXJpb3IgcGFyYSBhY2VsZXJhciBsYSBlamVjdWNpw7NuLg0KDQpgYGB7cn0NCiMgQ2FyZ2Ftb3MgZWwgZmljaGVybyBiaW5hcmlvDQpsb2FkKGZpbGUgPScuL2RhdGEvMjAyNC0wMy0xMi5SZGF0YScpDQpkaW0obXlkZikNCmhlYWQobXlkZikNCg0KbXlkZiAlPiUgaGVhZA0KbXlkZiAlPiUgdGFpbA0KbXlkZiAlPiUgc3RyDQpteWRmICU+JSBkaW0NCg0KYGBgDQoyLiBQYXJhIGZhY2lsaXRhciBlbCBtYW5lam8gdHJhbnNmb3JtYSBsb3MgZGF0b3MgZW4gdW4gdGliYmxlIGBhc190aWJibGVgIHkgYWxtYWNlbmEgZWwgcmVzdWx0YWRvIGNvbiBlbCBub21icmUgYGNyYW5gLCB5IGJvcnJhIGVsIGRhdGEgZnJhbWUgb3JpZ2luYWwgKHVzYSBgcm1gKQ0KYGBge3J9DQojIFRyYW5zZm9ybWFyIGVsIGRhdGEgZnJhbWUgZW4gdW4gdGliYmxlDQpjcmFuIDwtIGFzX3RpYmJsZShteWRmKQ0KDQojIEVsaW1pbmFyIGVsIGRhdGEgZnJhbWUgb3JpZ2luYWwgcGFyYSBsaWJlcmFyIG1lbW9yaWENCnJtKG15ZGYpDQoNCg0KYGBgDQozLiBVbmEgdmVudGFqYSBkZSB0cmFzZm9ybWFyIGVuIHRpYmJsZSBlcyBsYSB2aXN1YWxpemFjacOzbi4gRXNjcmliZSBkaXJlY3RhbWVudGUgZWwgbm9tYnJlIGRlIGxhIHZhcmlhYmxlIHkgb2JzZXJ2YSBsYSB2aXN1YWxpemFjacOzbi4gUG9zdGVyaW9ybWVudGUgdXNhIGxhIGZ1bmNpw7NuIGBnbGltcHNlYCBzb2JyZSBgY3JhbmAgeSBvYnNlcnZhIGVsIHJlc3VsdGFkby4NCmBgYHtyfQ0KIyBWaXN1YWxpemFyIGVsIHRpYmJsZSBkaXJlY3RhbWVudGUNCmNyYW4NCg0KIyBVc2FyIGxhIGZ1bmNpw7NuIGdsaW1wc2UgcGFyYSBvYnRlbmVyIHVuIHJlc3VtZW4gZGV0YWxsYWRvDQpnbGltcHNlKGNyYW4pDQoNCg0KYGBgDQojIGRwbHlyDQoqKk5vIGFsbWFjZW5lcyBlbCByZXN1bHRhZG8gZGUgbGFzIG9wZXJhY2lvbmVzLCBzYWx2byBxdWUgc2VhIG5lY2VzYXJpbyoqDQoNCjQuIEVsaWdlIMO6bmljYW1lbnRlIGxhcyB2YXJpYWJsZXMgYGlwX2lkLCBwYWNrYWdlLCBjb3VudHJ5YA0KYGBge3J9DQojIFNlbGVjY2lvbmFyIHNvbG8gbGFzIGNvbHVtbmFzIGlwX2lkLCBwYWNrYWdlIHkgY291bnRyeQ0KY3Jhbl9zZWxlY3RlZCA8LSBjcmFuICU+JQ0KICBzZWxlY3QoaXBfaWQsIHBhY2thZ2UsIGNvdW50cnkpDQoNCiMgVmVyIGVsIHJlc3VsdGFkbw0KaGVhZChjcmFuX3NlbGVjdGVkKQ0KDQpgYGANCjUuIEVsaWdlIHRvZGFzIGxhcyB2YXJpYWJsZXMgZW50cmUgYHJfYXJjaCB5IGNvdW50cnlgIA0KYGBge3J9DQoNCmNyYW5fcmFuZ2UgPC0gY3JhbiAlPiUNCiAgc2VsZWN0KHJfYXJjaDpjb3VudHJ5KQ0KDQpoZWFkKGNyYW5fcmFuZ2UpDQoNCmBgYA0KNS4gUHVlZGVzIHVzYXIgZWwgc2lnbm8gKC0pIHBhcmEgbm8gY29nZXIgdW5hIGNvbHVtbmEgZGV0ZXJtaW5hZGEuIFNlbGVjY2lvbmEgdG9kYXMgbGFzIGNvbHVtbmFzIG1lbm9zIGxhcyBxdWUgZXN0w6FuIGVudHJlIGBkYXRlIHkgc2l6ZWAgDQpgYGB7cn0NCmNyYW5fZXhjbHVkZWQgPC0gY3JhbiAlPiUNCiAgc2VsZWN0KC0oZGF0ZTpzaXplKSkNCg0KaGVhZChjcmFuX2V4Y2x1ZGVkKQ0KDQpgYGANCjYuIEVsaWdlIGFxdWVsbG9zIHJlZ2lzdHJvcyBlbiBsb3MgcXVlIHNlIGhheWEgZGVzY2FyZ2FkbyBlbCBwYXF1ZXRlIGBwbG90bHlgLCBkZXNkZSB1biBzaXN0ZW1hIHF1ZSBlamVjdXRhIGxhIHZlcnNpw7NuIGRlIFIgMy41LjENCmBgYHtyfQ0KY3Jhbl9wbG90bHkgPC0gY3JhbiAlPiUNCiAgZmlsdGVyKHBhY2thZ2UgPT0gInBsb3RseSIsIHJfdmVyc2lvbiA9PSAiMy41LjEiKQ0KDQpoZWFkKGNyYW5fcGxvdGx5KQ0KDQpgYGANCjYuIEVsaWdlIGFxdWVsbGFzIGRlc2NhcmdhcyByZWFsaXphZGFzIGRlc2RlIEVzcGHDsWEgKEVTKSwgY29uIHZlcnNpb25lcyBkZSBSIHN1cGVyaW9yZXMgYSBsYSAzLjQuMA0KYGBge3J9DQpjcmFuX2VzIDwtIGNyYW4gJT4lDQogIGZpbHRlcihjb3VudHJ5ID09ICJFUyIsIHJfdmVyc2lvbiA+ICIzLjQuMCIpDQoNCmhlYWQoY3Jhbl9lcykNCg0KYGBgDQoNCjcuIEVsaWdlIGFxdWVsbGFzIGRlc2NhcmdhcyByZWFsaXphZGFzIGRlc2RlIEVzcGHDsWEgKEVTKSwgbyBQb3J0dWdhbCAoUFQpLg0KYGBge3J9DQpjcmFuX2VzX3B0IDwtIGNyYW4gJT4lDQogIGZpbHRlcihjb3VudHJ5ICVpbiUgYygiRVMiLCAiUFQiKSkNCg0KaGVhZChjcmFuX2VzX3B0KQ0KDQpgYGANCg0KOC4gRWxpZ2UgYXF1ZWxsYXMgZGVzY2FyZ2FzIGN1eW8gdGFtYcOxbyBzdXBlcmEgbG9zIDEwMDUwMCBieXRlcyBkZXNkZSB1biBzaXN0ZW1hIG9wZXJhdGl2byAgYGxpbnV4LWdudWAuDQpgYGB7cn0NCmNyYW5fdGFtYW5vIDwtIGNyYW4gJT4lDQogIGZpbHRlcihzaXplID4gMTAwNTAwLCByX29zID09ICJsaW51eC1nbnUiKQ0KDQpjcmFuX3RhbWFubw0KYGBgDQo5LiBFbGlnZSBhcXVlbGxhcyBkZXNjYXJnYXMgZW4gbGFzIHF1ZSBsYSB2ZXJzacOzbiBkZWwgc2lzdGVtYSBvcGVyYXRpdm8gbm8gZXN0w6EgdmFjw61hLg0KYGBge3J9DQpkZXNjYXJnYXNfZmlsdHJhZGFzIDwtIGNyYW4gJT4lDQogIGZpbHRlcihpZl9hbGwoZXZlcnl0aGluZygpLCB+IC4gIT0gIiIpKQ0KZGVzY2FyZ2FzX2ZpbHRyYWRhcw0KYGBgDQoxMC4gQWxtYWNlbmEgZW4gYGNyYW4yYCB0b2RhcyBsYXMgY29sdW1uYXMgZW50cmUgYHNpemVgIHkgYGlwX2lkYC4gDQpgYGB7cn0NCmNyYW4yIDwtIGNyYW4lPiUNCiAgc2VsZWN0KHNpemU6aXBfaWQpDQpjcmFuMg0KYGBgDQoxMS4gT3JkZW5hIGBjcmFuMmAgZW4gb3JkZW4gYXNjZW5kZW50ZSBzZWfDum4gbGEgdmFyaWFibGUgYGlwX2lkYC4NCmBgYHtyfQ0KY3JhbjJfYXNjIDwtIGNyYW4yICU+JQ0KICBhcnJhbmdlKGlwX2lkKQ0KY3JhbjJfYXNjDQpgYGANCjEyLiBPcmRlbmEgYGNyYW4yYCBlbiBvcmRlbiBkZXNjZW5kZW50ZSBzZWfDum4gbGEgdmFyaWFibGUgYGlwX2lkYC4NCmBgYHtyfQ0KY3JhbjJfZGVzYyA8LSBjcmFuMiAlPiUNCiAgYXJyYW5nZShkZXNjKGlwX2lkKSkNCmNyYW4yX2Rlc2MNCmBgYA0KMTIuIE9yZGVuYSBgY3JhbjJgIHNlZ8O6biBsb3MgdmFsb3JlcyBkZSBgcGFja2FnZSBlIGlwX2lkYC4NCmBgYHtyfQ0KY3JhbjJfb3JkZW5hZGEgPC0gY3JhbjIgJT4lDQogIGFycmFuZ2UocGFja2FnZSwgaXBfaWQpDQpjcmFuMl9vcmRlbmFkYQ0KYGBgDQoxMy4gT3JkZW5hIGBjcmFuMmAgc2Vnw7puIGxvcyB2YWxvcmVzIGRlIGBjb3VudHJ5YCAoYXNjZW5kZW50ZSksIGByX3ZlcnNpb25gIChkZXNkZW5jZW50ZSkgZSBgaXBfaWRgKGFzY2VuZGVudGUpLg0KYGBge3J9DQpjcmFuMl9vcmRlbmFkYTIgPC0gY3JhbjIgJT4lDQogIGFycmFuZ2UoZGVzYyhyX3ZlcnNpb24pLCBjb3VudHJ5LCBpcF9pZCkNCmNyYW4yX29yZGVuYWRhMg0KDQpgYGANCjE0LiBTZWxlY2Npb25hIGVsIHN1YmNvbmp1bnRvIGZvcm1hZG8gcG9yIGxhcyB2YXJpYWJsZXMsIGRlbCBgY3JhbmAsIGBpcF9pZCwgcGFja2FnZSB5IHNpemVgIA0KZW4gZXN0ZSBvcmRlbiB5IGFsbWFjw6luYWxhcyBlbiBgY3JhbjNgLg0KYGBge3J9DQpjcmFuMyA8LSBjcmFuICU+JQ0KICBzZWxlY3QoaXBfaWQsIHBhY2thZ2UsIHNpemUpDQoNCmBgYA0KDQoxNS4gQ3JlYSB1bmEgbnVldmEgdmFyaWFibGUgZW4gYGNyYW4zYCBsbGFtYWRhIGBzaXplX21iYCBxdWUgY29udGVuZ2EgZWwgdGFtYcOxbyBkZSBsYSBkZXNjYXJnYSBleHByZXNhZG8gZW4gTWJ5dGVzICgkMU1ieXRlPTJeezIwfSQgYnl0ZXMpLg0KYGBge3J9DQpjcmFuMyA8LSBjcmFuMyU+JQ0KICBtdXRhdGUoKHNpemVfbWIgPSBzaXplIC8gMl4yMCkpDQpjcmFuMw0KDQpgYGANCjE2LiBTYWJpZW5kbyBxdWUgMUdiIHNvbiAkMl57MTB9JCBNYnl0ZXMsIGNyZWEgdW5hIG51ZXZhIHZhcmlhYmxlIGVuIGBjcmFuM2AgbGxhbWFkYSBgc2l6ZV9nYmAgcXVlIGNvbnRlbmdhIGVsIHRhbWHDsW8gZGUgbGEgZGVzY2FyZ2EgZXhwcmVzYWRvIGVuIEdieXRlcy4NCmBgYHtyfQ0KY3JhbjMgPC0gY3JhbjMgJT4lDQogIG11dGF0ZShzaXplX2diID0gc2l6ZSAvICgyXjIwICogMl4xMCkpDQpjcmFuMw0KYGBgDQoxNy4gQ29uc2lkZXJhIHF1ZSBzZSBoYSBkZXRlY3RhZG8gdW4gZXJyb3IgZW4gZWwgc2lzdGVtYSBkb25kZSB0b2RvcyBsb3MgdGFtYcOxb3MgZGUgbGFzIGRlc2NhcmdhcyBzb24gMTAwMCBieXRlcyBtZW5vcmVzIGRlIGxvIHF1ZSBjb3JyZXNwb25kZS4gQ3JlYSB1bmEgbnVldmEgdmFyaWFibGUgbGxhbWFkYSBgY29ycmVjdF9zaXplYCBjb24gZWwgdmFsb3IgY29ycmVjdG8uIA0KYGBge3J9DQpjcmFuMyA8LSBjcmFuMyAlPiUNCiAgbXV0YXRlKGNvcnJlY3Rfc2l6ZSA9IHNpemUgKyAxMDAwKQ0KY3JhbjMNCmBgYA0KMTguIFVzYSBgc3VtbWFyaXNlYCBwYXJhIGRldGVybWluYXIgZWwgdmFsb3IgbWVkaW8gZGVsIHRhbWHDsW8gZGUgbGFzIGRlc2NhcmdhcyByZWFsaXphZGFzIGVuIGBjcmFuYCB5IGFsbWFjZW7DoWxvIGVuIGxhIHZhcmlhYmxlIGBhdmdfYnl0ZXNgDQpgYGB7cn0NCmF2Z19ieXRlcyA8LSBjcmFuICU+JQ0KICBzdW1tYXJpc2UoYXZnX2J5dGVzID0gbWVhbihzaXplLCBuYS5ybSA9IFRSVUUpKQ0KYXZnX2J5dGVzDQpgYGANCiMgQWdydXBhbWllbnRvcw0KDQoxOS4gQ3JlYSB1biBhZ3J1cGFtaWVudG8gZGVsIHRpYmJsZSBgY3JhbmAgc2Vnw7puIGxhIHZhcmlhYmxlIGBwYWNrYWdlYCB5IGFsbWFjw6luYWxvIGVuIGxhIHZhcmlhYmxlIGBieV9wYWNrYWdlYC4gT2JzZXJ2YSBlbCBudWV2byBlbGVtZW50byBjcmVhZG8geSBhdW5xdWUgYXBhcmVudGVtZW50ZSBubyBoYXkgY2FtYmlvIGFsIHByaW5jaXBpbywgc2UgaW5kaWNhIHF1ZSBzZSBoYSBoZWNobyB1biBhZ3J1cGFtaWVudG8uIMOJc3RlIGFncnVwYW1pZW50byBzZSBwdWVkZSBkZXNoYWNlciBjb24gbGEgZnVuY2nDs24gYHVuZ3JvdXBgLg0KYGBge3J9DQpieV9wYWNrYWdlIDwtIGNyYW4gJT4lDQogIGdyb3VwX2J5KHBhY2thZ2UpDQogYnlfcGFja2FnZQ0KDQpieV9wYWNrYWdlX3VuZ3JvdXBlZCA8LSBieV9wYWNrYWdlICU+JQ0KICB1bmdyb3VwKCkNCg0KYGBgDQoNCjIwLiBVc2EgbGEgZnVuY2nDs24gYHN1bW1hcmlzZWAgc29icmUgIGVsIGVsZW1lbnRvIGFncnVwYWRvIHkgY2FsY3VsYSAgZWwgdmFsb3IgbWVkaW8gZGUgbGEgdmFyaWFibGUgYHNpemVgLiBPYnNlcnZhIGVsIGVmZWN0byBkZWwgYWdydXBhbWllbnRvIGVuIGVsIHJlc3VsdGFkby4NCmBgYHtyfQ0KbWVhbl9zaXplX3BhY2thZ2UgPC0gYnlfcGFja2FnZSAlPiUNCiAgc3VtbWFyaXNlKG1lYW5fc2l6ZSA9IG1lYW4oc2l6ZSwgbmEucm0gPSBUUlVFKSkNCm1lYW5fc2l6ZV9wYWNrYWdlDQpgYGANCjIxLiBQYXJhIGVsIGFncnVwYW1pZW50byBjcmVhZG8gY2FsY3VsYTogDQorIC4gY291bnQgPSBuKCkNCisgLiB1bmlxdWUgPSBuX2Rpc3RpbmN0KGlwX2lkKQ0KKyAuIGNvdW50cmllcyA9IG5fZGlzdGluY3QoY291bnRyeSkNCisgLiBhdmdfYnl0ZXMgPSBtZWFuKHNpemUpDQoNCm7Dum1lcm8gZGUgZWxlbWVudG9zOyBgbigpYCwgDQplbGVtZW50b3MgZGlzdGludG9zLGBuX2Rpc3RpbmN0KClgIGRlIGxhIHZhcmlhYmxlcyBgaXBfaWQgeSBjb3VudHJ5YCwgeSBlbCANCmxhIG1lZGlhbmEgKGZ1bmNpw7NuIGBzdGF0czo6bWVkaWFuYCkgZGUgbGEgdmFyaWFibGUgYHNpemVgIHkgYWxtYWPDqW5hbG9zIGVuIGxhcyB2YXJpYWJsZXMgYGNvdW50LCB1bmlxdWUsIGNvdW50cmllcyB5IG1lZF9ieXRlc2AgcmVzcGVjdGl2YW1lbnRlLiBFbCByZXN1bHRhZG8gZGVsIHN1bWFyaW8gc2UgYWxtYWNlbmFyw6EgZW4gYHBhY2tfc3VtYC4NCg0KYGBge3J9DQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0KYGBgDQoyMi4gTXVlc3RyYSBlbCAxJSBkZSBsb3MgcGFxdWV0ZXMgcXVlIGhhbiBzaWRvIG3DoXMgZGVzY2FyZ2Fkb3MuIFBhcmEgZWxsbyBwdWVkZXMgdXRpbGl6YXIgbGEgZnVuY2nDs24gYHF1YW50aWxlKD8/Pz8sIHByb2JzPTAuOTkpYC4gU2llbmRvID8/PyBsYSB2YXJpYWJsZSBkZSBpbnRlcsOpcywgcXVlIHRlIHBlcm1pdGlyw6EgZXN0aW1hciBlbCB1bWJyYWwuIExvcyB2YWxvcmVzIHBvciBlbmNpbWEgc2Vyw6FuIGxvcyBxdWUgbm9zIGludGVyZXNlbi4gQWxtYWNlbmEgZXN0b3MgcGFxdWV0ZXMgKip0b3AgZGVzY2FyZ2FzKiogZW4gbGEgdmFyaWFibGUgYHRvcF9jb3VudHNgLiAqKk5PVEE6IERlIGFob3JhIGVuIGFkZWxhbnRlIGNvbmNhdGVuYSB0b2RhcyBsYXMgb3BlcmFjaW9uZXMgcGFydGllbmRvIGRlbCBlbGVtZW50byBgY3JhbmAsIHVzYW5kbyBfcGlwZXNfKiouDQpgYGB7cn0NCg0KDQoNCg0KDQoNCmBgYA0KMjMuIFJlcGl0ZSBlbCBhcGFydGFkbyBhbnRlcmlvciB1dGlsaXphbmRvICoqcGlwZXMqKiBwYXJhIHF1ZSBmaW5hbG1lbnRlIGVsIHJlc3VsdGFkbyBzZSBvcmRlbmUsIGVuIG9yZGVuIGRlc2NlbmRlbnRlLCBzZWfDum4gZWwgbsO6bWVybyBkZSBkZXNjYXJnYXMgKGBjb3VudGApIHkgc2UgYWxtYWNlbmUgZWwgbGEgdmFyaWFibGUgYHRvcF9jb3VudHNfc29ydGVkYA0KYGBge3J9DQoNCg0KDQoNCg0KDQoNCg0KDQpgYGANCg0KMjQuIEVuIGx1Z2FyIGRlIHV0aWxpemFyIGVsIHJlY3VlbnRvIGRlIGRlc2NhcmdhcywgZXMgbcOhcyBsw7NnaWNvIGNvbnNpZGVyYXIgZWwgbsO6bWVybyBkZSBvcmRlbmFkb3JlcyBkaXN0aW50b3MgZW4gbG9zIHF1ZSBzZSBoYW4gZGVzY2FyZ2FkbyBsYXMgbGlicmVyw61hcywgcGFyYSBlbGxvIHV0aWxpemFyw61hbW9zIGxhIGNvbHVtbmEgYHVuaXF1ZWAgcXVlIGN1ZW50YSBzb2xvIHVuYSBkZXNjYXJnYSBwb3Igb3JkZW5hZG9yLCBpbmRlcGVuZGllbnRlbWVudGUgZGVsIG7Dum1lcm8gZGUgdmVjZXMgcXVlIHNlIGRlc2NhcmdlIHVuYSBsaWJyZXLDrWEuIFJlcGl0ZSBsb3MgY8OhbGN1bG9zIGNvbnNpZGVyYW5kbyBlc3RhIHZhcmlhYmxlIHkgYWxtYWNlbmEgZWwgcmVzdWx0YWRvIGVuIGB0b3BfdW5pcXVlX3NvcnRlZGAuDQoNCmBgYHtyfQ0KDQoNCg0KDQoNCg0KDQoNCmBgYA0KMjUuIENvbXBsZXRhIGxvIHF1ZSBzZSBpbmRpY2EgIHVzYW5kbyBgcGlwZXNgLiBTZWxlY2Npb25hIGxhcyBjb2x1bW5hcyBgaXBfaWQsIGNvdW50cnksIHBhY2thZ2UsIHNpemVgIGRlIGBjcmFuYCB5IG11ZXN0cmEgZWwgcmVzdWx0YWRvIHBvciBwYW50YWxsYSB1c2FuZG8gYHByaW50YC4gTm8gZXMgbmVjZXNhcmlvIGFsbWFjZW5hci4NCg0KYGBge3J9DQoNCg0KDQpgYGANCg0KMjYuIEHDsWFkZSB1bmEgY29sdW1uYSBsbGFtYWRhIGBzaXplX21iYCBxdWUgY29udGVuZ2EgZWwgdGFtYcOxbyBkZSBjYWRhIGRlc2NhcmdhIGVuIG1lZ2FieXRlcy4NCg0KYGBge3J9DQoNCg0KDQpgYGANCg0KMjcuIFNlbGVjY2lvbmEgYXF1ZWxsYXMgZGVzY2FyZ2FzIGN1eW8gdGFtYcOxbyBlcyBtZW5vciBvIGlndWFsIHF1ZSAwLjUgTWJ5dGVzDQpgYGB7cn0NCg0KDQoNCmBgYA0KMjguIFJlcGl0ZSBlbCBhcGFydGFkbyBhbnRlcmlvciBvcmRlbmFuZG8gbG9zIHJlc3VsdGFkb3MsIGVuIG9yZGVuIGRlc2NlbmRlbnRlIHNlZ8O6biBlbCB0YW1hw7FvIGVuIE1lZ2FieXRlcyB5IG11w6lzdHJhbG8gcG9yIHBhbnRhbGxhLg0KYGBge3J9DQoNCg0KDQoNCmBgYA0KKipFWFRSQSoqLiBTZSBkZXNlYSBtb3N0YXIgZ3LDoWZpY2FtZW50ZSBhcXVlbGxvcyBwYcOtc2VzIHF1ZSBzdXBvbmVuIGVsIG1heW9yIHRyw6FmaWNvIGRlIGluZm9ybWFjacOzbiwgb2Nhc2lvbmFkbyBwb3IgZWwgdG90YWwgZGUgZGVzY2FyZ2FzIGFjdW11bGFkYXMuIFBhcmEgZmFjaWxpdGFyIGxhIHZpc3VhbGl6YWNpw7NuIGPDqW50cmF0ZSBlbiBhcXVlbGxvcyBxdWUgZXN0w6FuIHBvciBlbmNpbWEgZGVsIDkwICUgZGVsIHRvcCBkZSBkZXNjYXJnYXMuIEVsIGdyw6FmaWNvIGRlYmUgbW9zdHJhciBsb3MgdmFsb3JlcyBkZSBmb3JtYSBvcmRlbmFkYSBkZSBtYXlvciBhIG1lbm9yLiBFbiBlbCBlamUgWCBkZWJlIGFwYXJlY2VyIGVsIGPDs2RpZ28gZGUgcGHDrXMgeSBlbiBlbCBlamUgWSBlbCB0csOhZmljbyBnZW5lcmFkby4gSW50ZW50YSBoYWNlcmxvIHVzYW5kbyB1bmEgdHViZXLDrWEgc2luIGVtcGxlYXIgbmluZ3VuYSB2YXJpYWJsZSBhdXhpbGlhci4NCmBjcmFuJT4lLi4uK2dlb21fcG9pbnQoKWANCg0KYGBge3J9DQoNCg0KDQoNCmBgYA0KKipFWFRSQSoqIFRhbCBjb21vIHNlIGluZGljYSBlbiBlbCBlamVyY2ljaW8sIGVsIHNpZ25pZmljYWRvIGRlIGxvcyBjw7NkaWdvcyB1c2Fkb3MgcGFyYSBsYSBjb2RpZmljYWNpw7NuIGRlIGxvcyBwYWlzZSBzZSBwdWVkZW4gb2J0ZW5lciBkZSBodHRwczovL2Rldi5tYXhtaW5kLmNvbS9nZW9pcC9nZW9pcDIvZ2VvbGl0ZTIvLiBEZXNjYXJnYSBlbCBmaWNoZXJvICoqR2VvTGl0ZTIgQ291bnRyeSoqIGVuIGZvcm1hdG8gQ1NWLiBFc3RlIGZpY2hlcm8gY29udGllbmUgaW5mb3JtYWNpw7NuIGVuIHZhcmlvcyBpZGlvbWFzLiBVc2EgYEdlb0xpdGUyLUNvdW50cnktTG9jYXRpb25zLWVzLmNzdmAgcXVlIHRpZW5lIGxhIGluZm9ybWFjacOzbiBlbiBlc3Bhw7FvbC4gRGVzZWFtb3MgcG9kZXIgZGV0ZXJtaW5hciBsYXMgZGVzY2FyZ2FzIHBvciBjb250aW5lbnRlcyBwb3IgbG8gcXVlIG5lY2VzaXRhbW9zIGHDsWFkaXIgZXN0YSBpbmZvcm1hY2nDs24gYSBsb3MgZGF0b3MgZGUgbGFzIGRlc2NhcmdhcyBhbG1hY2VuYWRvcyBlbiBgY3JhbmAuIFByb2NlZGUgZGUgbGEgc2lndWllbnRlIGZvcm1hOg0KDQogKyBEZXNjYXJnYSBlbCBmaWNoZXJvIGRlIGPDs2RpZ29zIGRlIHBhaXNlcyBgR2VvTGl0ZTItQ291bnRyeS1Mb2NhdGlvbnMtZXMuY3N2YCBlIGltcMOzcnRhbG8gZW4gZWwgZGF0YWZyYW1lIGBjb2RlX3BhaXNlc2AuICoqT0pPIGNvbiBsYSBjb2RpZmljYWNpw7NuKioNCiArIENhcmdhIHkgYWxtYWNlbmEgbGEgaW5mb3JtYWNpw7NuIGRlbCBmaWNoZXJvIGRlIGxvcyBwYWlzZXMuDQogKyBFbGltaW5hIHRvZG9zIGxvcyByZWdpc3Ryb3MgZGUgYGNyYW5gIHF1ZSBubyBpbmNsdXlhbiBlbCBjw7NkaWdvIGRlbCBwYcOtcy4NCiArIEVsaW1pbmEgbG9zIHJlZ2lzdHJvcyBkZSBgY29kZV9wYWlzZXNgIHF1ZSBubyBpbmNsdXllbiBlbCBjw7NkaWdvIGRlbCBwYcOtcy4NCiArIENvbWJpbmEgbG9zIGRvcyBkYXRhIGZyYW1lcyBwYXJhIGHDsWFkaXIgYSBgY3JhbmAgZWwgY8OzZGlnbyBkZWwgcGHDrXMgeSBlbCBjb250aW5lbnRlLCBlbCByZXN0byBkZSB2YXJpYWJsZXMgbm8gc29uIG5lY2VzYXJpYXMuDQogKyBSZXByZXNlbnRhIHVuIGRpYWdyYW1hIGRlIGJhcnJhcyBjb24gZWwgZmx1am8gZGUgZGF0b3MsIHBvciBjb250aW5lbnRlLCBvcmRlbmFkb3MgZGUgbWF5b3IgYSBtZW5vci4gRXhwcmVzYSBlbCBmbHVqbyBkZSBkYXRvcyBlbiBUZXJhYnl0ZXMgKDFUYj0yXjQwYnl0ZXMpDQogDQpgYGB7cn0NCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0KYGBgDQoNCiMgdGlkeXIuDQpDYXJnYSBlbCBmaWNoZXJvIGRlIGRhdG9zIGBkYXRvc3RpZHlyLlJkYXRhYCBxdWUgY29udGllbmUgdmFyaW9zIGNvbmp1bnRvcyBkZSBkYXRvcyBxdWUgdmFtb3MgYSB1c2FyIGVuIGxvcyBzaWd1aWVudGVzIGVqZXJjaWNpb3MuDQoNCjI5LiBPYnNlcnZhIGVsIGRhdGEgZnJhbWUgYHN0dWRlbnRzYCA6DQogIGdyYWRlIG1hbGUgZmVtYWxlDQoxICAgICBBICAgIDEgICAgICA1DQoyICAgICBCICAgIDUgICAgICAwDQozICAgICBDICAgIDUgICAgICAyDQo0ICAgICBEICAgIDUgICAgICA1DQo1ICAgICBFICAgIDcgICAgICA0DQoNCkxhIGNvbHVtbmEgYGdyYWRlYCBlcyBsYSBjYWxpZmljYWNpw7NuIHkgbGFzIGRvcyBzaWd1aWVudGVzIGVsIG7Dum1lcm8gZGUgZXN0dWRpYW50ZXMgaG9tYnJlIHkgbXVqZXIgcXVlIGhhbiByZWNpYmlkbyBkaWNoYSBjYWxpZmljYWNpw7NuLg0Kwr8gQ3XDoWxlcyBzb24gbGFzIHZhcmlhYmxlcyA/LiBUcmFuc2bDs3JtYWxvIGVuIHVuIGNvbmp1bnRvIGB0aWR5YCBjb24gbGFzIHZhcmlhYmxlcywgYGdyYWRlLCBzZXggeSBjb3VudGANCmBgYHtyfQ0KDQoNCg0KYGBgDQozMC4gQ29uc2lkZXJhIGVsIGNvbmp1bnRvIMK0c3R1ZGVudHMywrQsIHF1ZSBlcyBzaW1pbGFyIGFsIHByaW1lcm8gcGVybyBxdWUgc2UgaGFuIHJlY29naWRvIGRhdG9zIGNvcnJlc3BvbmRpZW50ZXMgYSBkb3MgZ3J1cG9zLCAoYG1hbGVfMSwgZmVtYWxlXzFgLCBldGMpLiBUcmFuc2bDs3JtYWxvIGVuIHVuIGNvbmp1bnRvIGB0aWR5YCBhw7FhZGllbmRvIHVuYSBjb2x1bW5hIGFkaWNpb25hbCBjb3JyZXNwb25kaWVudGUgYWwgZ3J1cG8gKGxsYW1hIGEgbGEgbnVldmEgdmFyaWFibGUgYGNsYXNzYCkNCmBgYHtyfQ0KDQoNCg0KDQpgYGANCjMxLiBDb25zaWRlcmEgZWwgY29uanVudG8gYHN0dWRlbnRzM2AgeSBvYnNlcnZhIHBvciBxdcOpIG5vIGVzIHVuIGB0aWR5IGRhdGFgIC4gTGFzIHZhcmlhYmxlcyBmaW5hbGVzIGRlbCBjb25qdW50byBkZWJlcsOtYW4gc2VyOiBgbmFtZSwgY2xhc3MsIG1pZHRlcm0sIGZpbmFsYC4gRGFkbyBxdWUgdW4gYWx1bW5vIHNvbG8gcHVlZGUgZXN0YXIgZW4gdW5hIGRlIGxhcyBjbGFzZXMgYXBhcmVjZW4gbXVjaG9zIGBOQWAsIGFsIHJlb3JnYW5pemFyLCB1c2EgYG5hLnJtPVRSVUVgIGVuIGxhcyBvcGNpb25lcyBkZSBgZ2F0aGVyYA0KYGBge3J9DQoNCg0KDQoNCmBgYA0KMzIuIENvbnNpZGVyYSBxdWUgbGEgY29sdW1uYSBgY2xhc3NgIHF1ZXJlbW9zIGVzcGVjaWZpY2FybGEgY29uIHZhbG9yZXMsIDEsMiwzLDQgeSA1IGVuIGx1Z2FyIGRlIGNsYXNzMSwgLi4uLCBjbGFzczUuIEVuIGxpYnJlcsOtYSBkZSBpbXBvcnRhY2nDs24gZGUgZGF0b3MgKGByZWFkcmApIHNlIHByb3BvcmNpb25hbiBmdW5jaW9uZXMgcXVlIHBlcm1pdGUgaWRlbnRpZmljYXIgeSBleHRyYWVyIGRpdmVyc29zIHRpcG9zIGRlIGRhdG9zLiBTb24gbGFzIGZ1bmNpb25lcyBgcGFyc2VfWFhYWGAuIE9ic2VydmEgZWwgZWZlY3RvIGRlIGFwbGljYXIgYHBhcnNlX251bWJlcignY2xhc3M1JylgIHkgdXRpbGl6YSBlbCByZXN1bHRhZG8gcGFyYSByZWFsaXphciBsYSB0YXJlYSBzb2xpY2l0YWRhLg0KDQpgYGB7cn0NCg0KDQoNCg0KDQoNCmBgYA0KMzMuIE9ic2VydmEgZWwgY29uanVudG8gYHN0dWRlbnRzNGAgZG9uZGUgc2UgcHJlc2VudGEgZWwgcHJvYmxlbWEgZGUgdGVuZXIgdmFyaWFzIG9ic2VydmFjaW9uZXMgZW4gbGEgbWlzbWEgdGFibGEuIFNlcGFyYSBlbCBjb25qdW50byBlbiBkb3MgdGFibGFzLCB1bmEgcXVlIGNvbnRlbmdhIGluZm9ybWFjacOzbiByZWxhdGl2YSBhIGxvcyBlc3R1ZGlhbnRlcywgcXVlIGxsYW1hcsOhcyBgc3R1ZGVudF9pbmZvYCwgcXVlIG5vIGRlYmUgY29udGVuZXIgcmVwZXRpY2lvbmVzLCB5IG90cmEgY29uIGxhcyBjYWxpZmljYWNpb25lcyB5IGxhIGNsYXZlIHByaW1hcmlhIHF1ZSBlbmxhemEgYW1iYXMgdGFibGFzKGBpZGApLCBxdWUgbGxhbWFyw6FzIGBncmFkZWJvb2tgLg0KYGBge3J9DQoNCg0KDQoNCg0KDQoNCg0KDQpgYGANCjM0LiBDb25zaWRlcmVtb3MgZWwgY2FzbyBkZSB1bmEgb2JzZXJ2YWNpw7NuIGFsbWFjZW5hZGEgZW4gdmFyaWFzIHRhYmxhcy4gT2JzZXJ2YSBsb3MgZGF0b3MgYWxtYWNlbmFkb3MgZW4gYGZhaWxlZGAgeSBgcGFzc2VkYCBjb24gbGEgaW5mb3JtYWNpw7NuIGRlIGxvcyBhbHVtbm9zIHF1ZSBzdXNwZWRlbiAoY2FsaWZpY2FjaW9uZXMgQyxELEUsRikgeSBsb3MgcXVlIGFwcnVlYmFuIChjYWxpZmljYWNpb25lcyBBLCBCKS4gRGViZXMgdW5pciBhbWJvcyBjb25qdW50b3MgZW4gdW5vIHF1ZSBjb250ZW5nYSB1bmEgY29sdW1uYSBhZGljaW9uYWwgbGxhbWFkYSBgc3RhdHVzYCBxdWUgaW5kaWNhcsOhIGBwYXNzZWRgIG8gYGZhaWxlZGAgc2Vnw7puIGxhIHNpdHVhY2nDs24gZGUgY2FkYSBhbHVtbm8sIHB1ZWRlcyB1c2FyIGxhIGZ1bmNpw7NuIGBiaW5kX3Jvd3NgLg0KYGBge3J9DQoNCg0KDQpgYGANCiMgUG9uZ2Ftb3MgdG9kbyBlbiBjb23Dum4uDQoNCjM1LiBFbCBTQVQgZXMgdW4gZXhhbWVuIHBvcHVsYXIgZGUgcHJlcGFyYWNpw7NuIHBhcmEgbGEgdW5pdmVyc2lkYWQgZW4gbG9zIEVzdGFkb3MgVW5pZG9zIHF1ZSBjb25zdGEgZGUgdHJlcyBwYXJ0ZXM6IGxlY3R1cmEgY3LDrXRpY2EsIG1hdGVtw6F0aWNhcyB5IGVzY3JpdHVyYS4gTG9zIGVzdHVkaWFudGVzIHB1ZWRlbiBvYnRlbmVyIGhhc3RhIDgwMCBwdW50b3MgZW4gY2FkYSBwYXJ0ZS4gRXN0ZSBjb25qdW50byBkZSBkYXRvcyBwcmVzZW50YSBlbCBuw7ptZXJvIHRvdGFsIGRlIGVzdHVkaWFudGVzLCBwYXJhIGNhZGEgY29tYmluYWNpw7NuIGRlIHBhcnRlIHkgc2V4byBkZWwgZXN0dWRpYW50ZS4gDQpDb25zaWRlcmEgbG9zIGRhdG9zIGFsbWFjZW5hZG8gZW4gYHNhdGAgeSBoYXogbGFzIHRyYW5zZm9ybWFjaW9uZXMgYWRlY3VhZGFzIHBhcmEgcXVlIGxhcyBjb2x1bW5hcyBkZWwgY29uanVudG8gYHRpZHlgIHNlYW4gYHNjb3JlX3JhbmdlLCBwYXJ0LCBzZXgsIGNvdW50YCBlbiBlc2Ugb3JkZW4uIFRlbiBlbiBjdWVudGEgcXVlIGxvcyBkYXRvcyB0b3RhbGVzIG5vIGVzIG5lY2VzYXJpbyBhbG1hY2VuYXJsb3MgeWEgcXVlIHNlIHB1ZWRlbiBnZW5lcmFyIGEgcG9zdGVyaW9yaS4NCg0KYGBge3J9DQoNCg0KDQoNCg0KDQpgYGANCg0KMzYuIFV0aWxpemEgYWdydXBhbWllbnRvcywgc2Vnw7puIGxhcyB2YXJpYWJsZXMgYHBhcnQsIHNleGAgeSBnZW5lcmEgZG9zIG51ZXZhcyBjb2x1bW5hcyBxdWUgY29udGVuZ2FuIGVsIG7Dum1lcm8gdG90YWwgZGUgZXN0dWRpYW50ZXMgZW4gZGljaG8gZ3J1cG8sIHkgbGEgcHJvcG9yY2nDs24gZGVudHJvIGRlbCBncnVwby4gQWxtYWPDqW5hbGFzIGVuIGxhcyB2YXJpYWJsZXMgYHRvdGFsIHkgcHJvcGAgcmVzcGVjdGl2YW1lbnRlLg0KYGBge3J9DQoNCg0KDQoNCg0KDQoNCg0KDQpgYGANCjM3LiBSZXByZXNlbnRhIGdyw6FmaWNhbWVudGUgZWwgY29uanVudG8gb2J0ZW5pZG8gcGFyYSBtb3N0cmFyIGxhcyBkaWZlcmVuY2lhcyBlbnRyZSBob21icmVzIHkgbXVqZXJlcywgZW4gbGFzIGRpZmVyZW50ZXMgcHJ1ZWJhcyBwYXJhIGNhZGEgdW5hIGRlIGxhcyBmcmFuamFzIGRlIGNhbGlmaWNhY2nDs24uDQpgYGB7cn0NCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCmBgYA0KDQoNCiMgTWFuZWpvIGRlIGZlY2hhcyBjb24gbHVicmlkYXRlLg0KDQozOC4gTGEgZnVuY2nDs24gYHRvZGF5YCBkZXZ1ZWx2ZSBsYSBmZWNoYSBhY3R1YWwuIEFsbWFjw6luYSBsYSBmZWNoYSBhY3R1YWwgZW4gYHRoaXNfZGF5YCwgeSBtdcOpc3RyYWxhIHBvciBwYW50YWxsYS4gVXNhIGxhcyBmdW5jaW9uZXMgYHllYXIsIG1vbnRoIHkgZGF5YCBwYXJhIGV4dHJhZXIgY2FkYSB1bmEgZGUgbGFzIHBhcnRlcy4gYHdkYXlgIHBhcmEgb2J0ZW5lciBlbCBkw61hIGRlIGxhIHNlbWFuYSAoKQ0KYGBge3J9DQoNCg0KDQoNCg0KDQoNCmBgYA0KMzkuIEd1YXJkYSBlbCBpbnN0YW50ZSBhY3R1YWwgZW4gdW5hIHZhcmlhYmxlIGxsYW1hZGEgYHRoaXNfbW9tZW50YCBjb24gbGEgZnVuY2nDs24gYG5vd2ANCnkgbXXDqXN0cmFsbyBwb3IgcGFudGFsbGEuIFVzYSBsYXMgZnVuY2nDs25lcyBgaG91ciwgbWludXRlLCBzZWNvbmRgIHBhcmEgZXh0cmFlciBsYSBpbmZvcm1hY2nDs24gZGUgY2FkYSBlbGVtZW50by4NCmBgYHtyfQ0KDQoNCg0KDQoNCmBgYA0KNDAuIExhIGxpYnJlcsOtYSBgbHVicmlkYXRlYCBkaXNwb25lIGRlIG3Dumx0aXBsZXMgZnVuY2lvbmVzIHBhcmEgaW1wb3J0YXIgZW4gZm9ybWF0byBmZWNoYSBkYXRvcyBjb24gdW4gZ3JhbiB2YXJpZWRhZCBkZSBmb3JtYXRvcyBkZSBlbnRyYWRhIGB5bWQoKSwgZG15KCksIGhtcygpLCB5bWRfaG1zKClgIGRvbmRlIGNhZGEgbGV0cmEgaW5kaWNhIF95ZWFycyAoeSksbW9udGhzIChtKSwgZGF5cyAoZCksIGhvdXJzIChoKSwgbWludXRlcyAobSksIHkgc2Vjb25kcyAocylfLiBQcnVlYmEgbXlfZGF0ZSA8LSB5bWQoIjIwMTktMDItMTQiKSwgeSBkZXRlcm1pbmEgZGUgcXVlIGNsYXNlIGVzIGBteV9kYXRlYC4gTGFzIGZ1bmNpb25lcyBzb24gbXV5IHJvYnVzdGFzIHkgcHVlZGVuIGludGVwcmV0YXIgY29ycmVjdGFtZW50ZSBkaXZlcnNvcyBmb3JtYXRvLiBQcnVlYmEgYHltZCgpYCBwYXJhIGltcG9ydGFyIGxhIGZlY2hhICBfXyIxOTg5IE1heSAxNyJfXy4gwr9Dw7NtbyBpbXBvcnRhcsOtYXMgX18iTWFyY2ggMTIsIDE5NzUiX18/DQpgYGB7cn0NCg0KDQoNCmBgYA0KNDEuIEltcG9ydGEgZWwgdmFsb3IgbnVtw6lyaWNvIGAyNTA4MTk4NWAsIHNhYmllbmRvIHF1ZSBlbCBmb3JtYXRvIGVzIGRpYSwgbWVzLCBhw7FvLCB5IHBvc3Rlcmlvcm1lbnRlIGAxOTIwMTJgIGRvbmRlIHNlIGhhIGFsbWFjZW5hZG8gYcOxbywgbWVzIHkgZMOtYS4gTW9kaWZpY2EgZWwgY2FtcG8gZmVjaGEgZW4gZWwgc2VndW5kbyBjYXNvIGHDsWFkaWVuZG8gYWxnw7puIGNhcsOhY3RlciBzZXBhcmFkb3IgcGFyYSBlbGltaW5hciBsYSBhbWJpZ8O8ZWRhZC4NCmBgYHtyfQ0KZmVjaGE8LScxOTIwMDEwMicNCnltZChmZWNoYSkNCg0KDQoNCg0KYGBgDQo0Mi4gQWxtYWNlbmEgbGEgY2FkZW5hIGAyMDE0LTA4LTIzIDE3OjIzOjAyYCBlbiBsYSB2YXJpYWJsZSBgZHQxYCAgZSBpbXDDs3J0YWxhIHBhcmEgcXVlIGluY2x1eWEgbGEgZmVjaGEgeSBsYSBob3JhLHBvc3Rlcmlvcm1lbnRlIGltcG9ydGEgbGEgY2FkZW5hIGAiMDM6MjI6MTQiYCAoaGg6bW06c3MpDQpgYGB7cn0NCg0KDQoNCg0KYGBgDQo0My4gSW1wb3J0YSBlbCB2ZWN0b3IgZGUgZmVjaGFzIGFsbWFjZW5hZG8gZWwgYGR0MmANCmBgYHtyfQ0KDQpgYGANCjQ0LiBQb2RlbW9zIHJlYXNpbmdhciBudWV2b3MgdmFsb3JlcyBhIHVuYSB2YXJpYWJsZSBmZWNoYS9ob3JhIGNvbiBsYSBmdW5jacOzbiBgdXBkYXRlYA0KLCBwb3IgZWplbXBsbzogYHVwZGF0ZSh0aGlzX21vbWVudCwgaG91cnMgPSA4LCBtaW51dGVzID0gMzQsIHNlY29uZHMgPSA1NSlgLiBBY3R1YWxpemEgbGEgdmFyaWFibGUgYHRoaXNfbW9tZW50YCBjcmVhZGEgYW50ZXJpb3JtZW50ZSBwYXJhIHF1ZSBjb250ZW5nYSBsYSBob3JhIGFjdHVhbC4NCmBgYHtyfQ0KDQpgYGANCjQ1LiBfX1pvbmFzIGhvcmFyaWFzX18uIENvbnNpZGVyYSBxdWUgZXN0w6FzIGVuIE51ZXZhIFlvcmssIHF1ZSBwbGFuZWFzIHZpc2l0YXIgYSB1biBhbWlnbyBlbiBIb25nIEtvbmcgeSBxdWUgaGFzIHBlcmRpZG8gdHUgaXRpbmVyYXJpbywgcGVybyBzYWJlcyBxdWUgdHUgdnVlbG8gc2FsZSBkZSBOdWV2YSBZb3JrIGEgbGFzIDE3OjM0ICgxNzozNCBob3JhcykgcGFzYWRvIG1hw7FhbmEuIFRhbWJpw6luIHNhYmVzIHF1ZSBlbCB2dWVsbyBlc3TDoSBwcm9ncmFtYWRvIHBhcmEgbGxlZ2FyIGEgSG9uZyBLb25nIGV4YWN0YW1lbnRlIDE1IGhvcmFzIHkgNTAgbWludXRvcyBkZXNwdcOpcyBkZSBsYSBzYWxpZGEuIFZhbW9zIGEgcmVjb25zdHJ1aXIgZWwgaXRpbmVyYXJpbzoNCiAgKyBHZW5lcmEgbGEgZmVjaGEgYWN0dWFsLCBjb24gYG5vd2AgZSBpbmRpY2EgbGEgem9uYSBob3JhcmlhIGRlIE51ZXZhIFlvcmsoQW1lcmljYS9OZXdfWW9yaykgKGxhIGZ1bmNpw7NuIGBPbHNvbk5hbWVzKClgIGRldnVlbHZlIHRvZGFzIGxhcyB6b25hcyBob3JhcmlhcykgeSBhbG1hY8OpbmFsYSBlbiBsYSB2YXJpYWJsZSBgbnljYC4NCiAgKyBBw7FhZGUgZG9zIGTDrWFzIHkgYWxtYWPDqW5hbG8gZW4gbGEgdmFyaWFibGUgYHNhbGlkYWAuIFB1ZWRlcyB1c2FyIGxhIGZ1bmNpw7NuIGBkYXlzYCB5IHN1bWFyIGRpcmVjdGFtZW50ZS4gQ29tcHJ1ZWJhIHF1ZSBsYSBzYWxpZGEgdGllbmUgZWwgdmFsb3IgY29ycmVjdG8uDQogICsgQWN0dWFsaXphIGxhIHZhcmlhYmxlIHNhbGlkYSBjb24gbGEgaG9yYSByZWFsIGRlIHNhbGlkYS4NCiAgKyBBdmVyaWd1YSBhIHF1w6kgaG9yYSBsbGVnYXLDoXMgYSBIb25nIEtvbmcsIGNvbiByZWZlcmVuY2lhIGhvcmFyaWEgZGUgTlkuDQogICsgQXZlcmlndWEgYSBxdcOpIGhvcmEgbGxlZ2Fyw6FzIGEgSG9uZyBLb25nLCBjb24gcmVmZXJlbmNpYSBob3JhcmlhIGxvY2FsLiBVc2EgbGEgZnVuY2nDs24gYHdpdGhfdHpgIHBhcmEgcmVhbGl6YXIgZXN0YSB0YXJlYS4NCiAgKyBMYSDDumx0aW1hIHZleiBxdWUgdmlzdGUgYSB0dSBhbWlnbyBmdWUgZW4gU2luZ2FwdXIgZWwgMTcgZGUgSnVuaW8gZGUgMjAwOC4gR2VuZXJhIGRpY2hhIGZlY2hhIGVuIHpvbmEgaG9yYXJpYSBkZSBTaW5nYXB1ciB5IHV0aWxpemEgbGEgZnVuY2lvbiBgaW50ZXJ2YWxgIHBhcmEgY3JlYXIgZWwgdHJhbW8gdGVtcG9yYWwgZW50cmUgYW1iYXMgZmVjaGFzIHkgYXBsaWNhIGxhIGZ1bmNpw7NuIGBhcy5wZXJpb2RgIHNvYnJlIGVsIGludGVydmFsbyByZXN1bHRhbnRlIHBhcmEgY2FsY3VsYXIgZXjDoWN0YW1lbnRlIGN1w6FudG8gdGllbXBvIGhhY2UgZGVzZGUgcXVlIG9zIHZpc3RlaXMuDQogIEN1YW5kbyBjb25zaWRlcmFtb3Mgem9uYXMgaG9yYXJpYXMsIHBlcmlkb3MgcXVlIGluY2x1eWVuIGHDsW9zIGJpc2llc3RvcywgZXRjLiBFbCBtYW5lam8gZGUgZmVjaGEgcmVxdWllcmUgZGUgbGlicmVyw61hcyBjb21vIF9fbHVicmlkYXRlX18gcGFyYSBmYWNpbGl0YXIgZXN0YXMgdGFyZWFzLg0KICANCmBgYHtyfQ0KDQoNCg0KDQoNCg0KDQoNCg0KYGBgDQogIA0KICANCg0K